// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// simple-client is an example VPP management application that exercises the
// govpp API on real-world use-cases.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"git.fd.io/govpp.git"
	"git.fd.io/govpp.git/adapter/socketclient"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core"
	"github.com/coreos/etcd/clientv3"
	"github.com/pan2za/qos-client/binapi/classify"
	interfaces "github.com/pan2za/qos-client/binapi/interface"
	"github.com/pan2za/qos-client/binapi/interface_types"
	"github.com/pan2za/qos-client/binapi/ip"
	"github.com/pan2za/qos-client/binapi/ip_types"
	"github.com/pan2za/qos-client/binapi/policer"
	"github.com/pan2za/qos-client/binapi/vpe"
)

var (
	sockAddr = flag.String("sock", socketclient.DefaultSocketName, "Path to VPP binary API socket file")
)

func client_connect_vpp(event *clientv3.Event) (api.Channel, error) {
	flag.Parse()

	fmt.Println("go connecting vpp...")
	fmt.Println()

	// connect to VPP asynchronously
	conn, connEv, err := govpp.AsyncConnect(*sockAddr, core.DefaultMaxReconnectAttempts, core.DefaultReconnectInterval)
	if err != nil {
		log.Fatalln("ERROR:", err)
	}
	defer conn.Disconnect()

	// wait for Connected event
	select {
	case e := <-connEv:
		if e.State != core.Connected {
			log.Fatalln("ERROR: connecting to VPP failed:", e.Error)
		}
	}

	// check compatibility of used messages
	ch, err := conn.NewAPIChannel()
	if err != nil {
		log.Fatalln("ERROR: creating channel failed:", err)
	}
	defer ch.Close()
	if err := ch.CheckCompatiblity(vpe.AllMessages()...); err != nil {
		log.Fatal(err)
	}
	if err := ch.CheckCompatiblity(interfaces.AllMessages()...); err != nil {
		log.Fatal(err)
	}

	// process errors encountered during the example
	defer func() {
		if len(errors) > 0 {
			fmt.Printf("finished with %d errors\n", len(errors))
			os.Exit(1)
		} else {
			fmt.Println("finished successfully")
		}
	}()

	interface_dump_at_startup(ch)
	// use request/reply (channel API)
	//getVppVersion(ch)
	send_message_vpp(ch, event)
	//getSystemTime(ch)
	//idx := createLoopback(ch)
	//interfaceDump(ch)
	//addIPAddress(ch, idx)
	//ipAddressDump(ch, idx)
	//interfaceNotifications(ch, idx)
	return ch, err
}

func send_message_vpp(ch api.Channel, event *clientv3.Event) {
	fmt.Println("recv key: ", string(event.Kv.Key), " ,event type: ", event.Type)
	if strings.Contains(string(event.Kv.Key), "/vpp/classify_table") {
		if event.Type == 0 {
			add_ct(ch, event)
		} else if event.Type == 1 {
			del_ct(ch, event)
		} else {

		}
	} else if strings.Contains(string(event.Kv.Key), "/vpp/policer") {
		if event.Type == 0 {
			add_policer(ch, event)
		} else if event.Type == 1 {
			del_policer(ch, event)
		} else {

		}
	} else if strings.Contains(string(event.Kv.Key), "/vpp/classify_session") {
		if event.Type == 0 {
			add_cs(ch, event)
		} else if event.Type == 1 {
			del_cs(ch, event)
		} else {

		}
	} else if strings.Contains(string(event.Kv.Key), "/vpp/set_policer_intf") {
		if event.Type == 0 {
			apply_poli(ch, event, true)
		} else if event.Type == 1 {
			apply_poli(ch, event, false)
		} else {

		}
	} else {
	}

}

func add_policer(ch api.Channel, event *clientv3.Event) {
	req := &policer.PolicerAddDel{}

	err := json.Unmarshal(event.Kv.Value, &req)
	if err != nil {
		log.Print(err)
	}

	reply := &policer.PolicerAddDelReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "policer add failed")
	}
	fmt.Printf("Event received! %s executed on %q with value %q\n", event.Type, event.Kv.Key, event.Kv.Value)
}

func del_policer(ch api.Channel, event *clientv3.Event) {
	str := string(event.Kv.Key)
	name := str[len("/vpp/policer/"):]

	req := &policer.PolicerAddDel{
		IsAdd: false,
		Name:  name,
	}
	reply := &policer.PolicerAddDelReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "policer del failed")
	}
}

func add_ct(ch api.Channel, event *clientv3.Event) api.MessageType {
	//var mask = []byte("hello world hell")
	var mask []byte
	var match uint32
	if strings.Compare(string(event.Kv.Value), "0") == 0 {
		match = 1
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00}

	} else if strings.Compare(string(event.Kv.Value), "1") == 0 {
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(event.Kv.Value), "2") == 0 {
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(event.Kv.Value), "3") == 0 {
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else if strings.Compare(string(event.Kv.Value), "4") == 0 {
		match = 2
		mask = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
			0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
	} else {

	}

	req := &classify.ClassifyAddDelTable{
		IsAdd:          true,
		DelChain:       false,
		TableIndex:     0xffffffff,
		Nbuckets:       2,
		MemorySize:     2 << 20,
		SkipNVectors:   1,
		MatchNVectors:  match,
		NextTableIndex: 0xffffffff,
		MissNextIndex:  0xffffffff,
		Mask:           mask,
	}
	reply := &classify.ClassifyAddDelTableReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify table add failed")
		return -1
	}
	fmt.Printf("Event received! %s executed on %q with value %q\n", event.Type, event.Kv.Key, event.Kv.Value)
	fmt.Printf("reply index: %d\n", reply.GetMessageType())
	return reply.GetMessageType()
}

func del_ct(ch api.Channel, event *clientv3.Event) {
	str := string(event.Kv.Key)
	index, _ := strconv.ParseUint(str[len("/vpp/classify_table/"):], 10, 32)

	index_uint_32 := uint32(index)

	req := &classify.ClassifyAddDelTable{
		IsAdd:      false,
		TableIndex: index_uint_32,
	}
	reply := &classify.ClassifyAddDelTableReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify table del failed")
	}
}

func add_cs(ch api.Channel, event *clientv3.Event) api.MessageType {
	// str := string(event.Kv.Key)
	// index := strings.LastIndex(str, "/")
	// table_idx, _ := strconv.ParseUint(str[index+1:], 10, 32)
	// table_idx_uint32 := uint32(table_idx)
	// hitnext_idx, _ := strconv.ParseUint(str[22:index], 10, 32)
	// hitnext_idx_uint32 := uint32(hitnext_idx)
	// var match = []byte("l3 ip4 src 1.1.1.2              ")
	// req := &classify.ClassifyAddDelSession{
	// 	IsAdd:        true,
	// 	TableIndex:   table_idx_uint32,
	// 	HitNextIndex: hitnext_idx_uint32,
	// 	OpaqueIndex:  0xffffffff,
	// 	Advance:      0,
	// 	Action:       0,
	// 	Metadata:     0,
	// 	Match:        match,
	// }
	req := &classify.ClassifyAddDelSession{}
	fmt.Println(string(event.Kv.Value))

	err := json.Unmarshal(event.Kv.Value, &req)
	if err != nil {
		log.Print(err)
	}
	fmt.Printf("req: %+v\n", req)

	reply := &classify.ClassifyAddDelSessionReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify table add failed")
		return -1
	}
	fmt.Printf("Event received! %s executed on %q with value %q\n", event.Type, event.Kv.Key, event.Kv.Value)
	return reply.GetMessageType()
}

func del_cs(ch api.Channel, event *clientv3.Event) {
	str := string(event.Kv.Key)
	fmt.Println("str: " + str)
	index := strings.LastIndex(str, "/")
	table_idx, _ := strconv.ParseUint(str[index+1:], 10, 32)
	table_idx_uint32 := uint32(table_idx)
	hitnext_idx, _ := strconv.ParseUint(str[22:index], 10, 32)
	hitnext_idx_uint32 := uint32(hitnext_idx)

	fmt.Println("table index :" + str[index+1:] + " hitnext idx: " + str[22:index])

	var match = []byte("l3 ip4 src 1.1.1.2              ")

	req := &classify.ClassifyAddDelSession{
		IsAdd:        false,
		TableIndex:   table_idx_uint32,
		HitNextIndex: hitnext_idx_uint32,
		Match:        match,
	}
	reply := &classify.ClassifyAddDelSessionReply{}
	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "classify session del failed.")
	}
}
func apply_poli(ch api.Channel, event *clientv3.Event, is_add bool) api.MessageType {
	key := string(event.Kv.Key)
	ifname := key[len("/vpp/set_policer_intf/"):]
	val := string(event.Kv.Value)
	table_idx, _ := strconv.ParseUint(val, 10, 32)
	ifidx, ok := get_ifidx(ifname)
	if ok{
		fmt.Printf("ifname: %s => ifidx %d", ifname, ifidx)
	}else{
		fmt.Println("not found such ifidx")
		return -1
	}
	fmt.Printf("table_idx %d", table_idx)
	req := &classify.PolicerClassifySetInterface{
		IsAdd: is_add,
		IP4TableIndex: uint32(table_idx),
		SwIfIndex:  interface_types.InterfaceIndex(ifidx),
	}

	reply := &classify.PolicerClassifySetInterfaceReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "apply policer to interface failed.")
		return -1
	}

	fmt.Printf("Event received. %s executed on %q with value %q \n", event.Type, event.Kv.Key, event.Kv.Value)
	return reply.GetMessageType()

}

func getVppVersion(ch api.Channel) {
	fmt.Println("Retrieving version..")

	req := &vpe.ShowVersion{}
	reply := &vpe.ShowVersionReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "retrieving version")
		return
	}

	fmt.Printf("VPP version: %q\n", reply.Version)
	fmt.Println("OK")
	fmt.Println()
}

func getSystemTime(ch api.Channel) {
	fmt.Println("Retrieving system time..")

	req := &vpe.ShowVpeSystemTime{}
	reply := &vpe.ShowVpeSystemTimeReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "retrieving system time")
		return
	}

	fmt.Printf("system time: %v\n", reply.VpeSystemTime)
	fmt.Println("OK")
	fmt.Println()
}

func createLoopback(ch api.Channel) interface_types.InterfaceIndex {
	fmt.Println("Creating loopback interface..")

	req := &interfaces.CreateLoopback{}
	reply := &interfaces.CreateLoopbackReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "creating loopback interface")
		return 0
	}

	fmt.Printf("interface index: %v\n", reply.SwIfIndex)
	fmt.Println("OK")
	fmt.Println()

	return reply.SwIfIndex
}

func interfaceDump(ch api.Channel) {
	fmt.Println("Dumping interfaces..")

	n := 0
	reqCtx := ch.SendMultiRequest(&interfaces.SwInterfaceDump{
		SwIfIndex: ^interface_types.InterfaceIndex(0),
	})
	for {
		msg := &interfaces.SwInterfaceDetails{}
		stop, err := reqCtx.ReceiveReply(msg)
		if stop {
			break
		}
		if err != nil {
			logError(err, "dumping interfaces")
			return
		}
		n++
		fmt.Printf(" - interface #%d: %+v\n", n, msg)
		marshal(msg)
	}

	fmt.Println("OK")
	fmt.Println()
}

func addIPAddress(ch api.Channel, index interface_types.InterfaceIndex) {
	fmt.Printf("Adding IP address to interface index %d\n", index)

	req := &interfaces.SwInterfaceAddDelAddress{
		SwIfIndex: index,
		IsAdd:     true,
		Prefix: ip_types.AddressWithPrefix{
			Address: ip_types.Address{
				Af: ip_types.ADDRESS_IP4,
				Un: ip_types.AddressUnionIP4(ip_types.IP4Address{10, 10, 0, uint8(index)}),
			},
			Len: 32,
		},
	}
	marshal(req)
	reply := &interfaces.SwInterfaceAddDelAddressReply{}

	if err := ch.SendRequest(req).ReceiveReply(reply); err != nil {
		logError(err, "adding IP address to interface")
		return
	}

	fmt.Println("OK")
	fmt.Println()
}

func ipAddressDump(ch api.Channel, index interface_types.InterfaceIndex) {
	fmt.Printf("Dumping IP addresses for interface index %d..\n", index)

	req := &ip.IPAddressDump{
		SwIfIndex: index,
	}
	reqCtx := ch.SendMultiRequest(req)

	for {
		msg := &ip.IPAddressDetails{}
		stop, err := reqCtx.ReceiveReply(msg)
		if err != nil {
			logError(err, "dumping IP addresses")
			return
		}
		if stop {
			break
		}
		fmt.Printf(" - ip address: %+v\n", msg)
		marshal(msg)
	}

	fmt.Println("OK")
	fmt.Println()
}

// interfaceNotifications shows the usage of notification API. Note that for notifications,
// you are supposed to create your own Go channel with your preferred buffer size. If the channel's
// buffer is full, the notifications will not be delivered into it.
func interfaceNotifications(ch api.Channel, index interface_types.InterfaceIndex) {
	fmt.Printf("Subscribing to notificaiton events for interface index %d\n", index)

	notifChan := make(chan api.Message, 100)

	// subscribe for specific notification message
	sub, err := ch.SubscribeNotification(notifChan, &interfaces.SwInterfaceEvent{})
	if err != nil {
		logError(err, "subscribing to interface events")
		return
	}

	// enable interface events in VPP
	err = ch.SendRequest(&interfaces.WantInterfaceEvents{
		PID:           uint32(os.Getpid()),
		EnableDisable: 1,
	}).ReceiveReply(&interfaces.WantInterfaceEventsReply{})
	if err != nil {
		logError(err, "enabling interface events")
		return
	}

	// receive notifications
	go func() {
		for notif := range notifChan {
			e := notif.(*interfaces.SwInterfaceEvent)
			fmt.Printf("incoming event: %+v\n", e)
			marshal(e)
		}
	}()

	// generate some events in VPP
	err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
		SwIfIndex: index,
		Flags:     interface_types.IF_STATUS_API_FLAG_ADMIN_UP,
	}).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
	if err != nil {
		logError(err, "setting interface flags")
		return
	}
	err = ch.SendRequest(&interfaces.SwInterfaceSetFlags{
		SwIfIndex: index,
		Flags:     0,
	}).ReceiveReply(&interfaces.SwInterfaceSetFlagsReply{})
	if err != nil {
		logError(err, "setting interface flags")
		return
	}

	// disable interface events in VPP
	err = ch.SendRequest(&interfaces.WantInterfaceEvents{
		PID:           uint32(os.Getpid()),
		EnableDisable: 0,
	}).ReceiveReply(&interfaces.WantInterfaceEventsReply{})
	if err != nil {
		logError(err, "setting interface flags")
		return
	}

	// unsubscribe from delivery of the notifications
	err = sub.Unsubscribe()
	if err != nil {
		logError(err, "unsubscribing from interface events")
		return
	}

	fmt.Println("OK")
	fmt.Println()
}

func marshal(v interface{}) {
	fmt.Printf("GO: %#v\n", v)
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		panic(err)
	}
	fmt.Printf("JSON: %s\n", b)
}

var errors []error

func logError(err error, msg string) {
	fmt.Printf("ERROR: %s: %v\n", msg, err)
	errors = append(errors, err)
}
